use crate::ffi;
use crate::ffi::{
    _signal_client, _signal_msg_t, buffer_t, ice_info_t, session_create_by_remote_t,
    session_ice_cands_update_t, session_id_t, session_sdp_info_t, signal_client_opt_t,
    signal_client_t, signal_error_e, signal_error_e_signal_error_fail,
    signal_error_e_signal_error_invalid_param, signal_error_e_signal_error_no_msg,
    signal_error_e_signal_error_ok, signal_msg_id_t_smi_remote_ice_candidate_update,
    signal_msg_id_t_smi_remote_sdp_arrived, signal_msg_id_t_smi_session_created_by_remote,
    signal_msg_t,
};
use crate::wsclient::WsClient;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ptr::null_mut;
use embed_std::ffi::{c_char, CStr};
use embed_std::sys_log::set_log_level;
use embed_std::{debugln, infoln};

macro_rules! WS {
    ($client:expr) => {{
        let client = unsafe { Box::leak(Box::from_raw($client)) };
        unsafe { Box::leak(Box::from_raw(client.priv_ as *mut WsClient)) }
    }};
}

pub extern "C" fn destroy_signal_msg(msg: *mut _signal_msg_t) {
    if msg.is_null() {
        return;
    }
    let m = unsafe { &mut *msg };
    if m.msg_id == signal_msg_id_t_smi_session_created_by_remote {
        if !m.data.data.is_null() && m.data.cap > 0 {
            let info = unsafe { &*(m.data.data as *const session_create_by_remote_t) };
            if !info.sdp.sdp.data.is_null() && info.sdp.sdp.cap > 0 {
                free_buffer_t_content(&info.sdp.sdp as *const buffer_t as *mut buffer_t);
            }
        }
    } else if m.msg_id == signal_msg_id_t_smi_remote_ice_candidate_update {
        if !m.data.data.is_null() && m.data.cap > 0 {
            let info = unsafe { &*(m.data.data as *const session_ice_cands_update_t) };
            free_buffer_t_content(&info.cands as *const buffer_t as *mut buffer_t);
        }
    } else if m.msg_id == signal_msg_id_t_smi_remote_sdp_arrived {
        if !m.data.data.is_null() && m.data.cap > 0 {
            let info = unsafe { &*(m.data.data as *const session_sdp_info_t) };
            free_buffer_t_content(&info.sdp as *const buffer_t as *mut buffer_t);
        }
    }
    free_buffer_t_content(&mut m.data);
}

pub extern "C" fn destroy(client: *mut _signal_client) {
    if client.is_null() {
        return;
    }
    let client = unsafe { Box::from_raw(client) };
    if !client.priv_.is_null() {
        let ws = unsafe { Box::from_raw(client.priv_ as *mut WsClient) };
        debugln!("drop WsClient");
        drop(ws);
    }
    debugln!("drop signal_client");
    drop(client);
}

pub extern "C" fn login(client: *mut _signal_client) -> signal_error_e {
    if client.is_null() {
        return signal_error_e_signal_error_invalid_param;
    }
    let wc = WS!(client);
    match wc.login() {
        Ok(_) => signal_error_e_signal_error_ok,
        Err(e) => e,
    }
}

pub extern "C" fn create_session(
    client: *mut _signal_client,
    peer_id: *const ::core::ffi::c_char,
    out_session_id: *mut session_id_t,
    out_ice_info: *mut ice_info_t,
) -> signal_error_e {
    if client.is_null() {
        return signal_error_e_signal_error_invalid_param;
    }
    let wc = WS!(client);
    let remote_pid = unsafe { CStr::from_ptr(peer_id) };
    match wc.create_session(remote_pid) {
        Ok(_) => signal_error_e_signal_error_ok,
        Err(e) => e,
    }
}

pub extern "C" fn destroy_session(client: *mut _signal_client, session_id: session_id_t) {
    if client.is_null() {
        return;
    }
    let wc = WS!(client);
    wc.destroy_session(session_id);
}

pub extern "C" fn update_local_sdp(
    client: *mut _signal_client,
    session_id: session_id_t,
    sdp: *const ::core::ffi::c_char,
) -> signal_error_e {
    if client.is_null() || sdp.is_null() {
        return signal_error_e_signal_error_invalid_param;
    }
    let wc = WS!(client);
    let s = unsafe { CStr::from_ptr(sdp) };
    match wc.update_local_sdp(session_id, s) {
        Ok(_) => signal_error_e_signal_error_ok,
        Err(e) => e,
    }
}

pub extern "C" fn update_ice_candidate(
    client: *mut _signal_client,
    session_id: session_id_t,
    sdp: *const ::core::ffi::c_char,
) -> signal_error_e {
    if client.is_null() || sdp.is_null() {
        return signal_error_e_signal_error_invalid_param;
    }
    let wc = WS!(client);
    let s = unsafe { CStr::from_ptr(sdp) };
    match wc.update_ice_candidate(session_id, s) {
        Ok(_) => signal_error_e_signal_error_ok,
        Err(e) => e,
    }
}

pub extern "C" fn handle_msg(
    client: *mut _signal_client,
    out_msg: *mut signal_msg_t,
    to_in_ms: u32,
) -> signal_error_e {
    if client.is_null() {
        return signal_error_e_signal_error_invalid_param;
    }
    let wc = WS!(client);
    match wc.handle_msg(to_in_ms) {
        Ok(msg) => match msg {
            Some(msg) => {
                if !out_msg.is_null() {
                    unsafe { *out_msg = msg };
                }
                signal_error_e_signal_error_ok
            }
            None => signal_error_e_signal_error_no_msg,
        },
        Err(e) => e,
    }
}

pub extern "C" fn is_connected(client: *mut _signal_client) -> u8 {
    if client.is_null() {
        return 0;
    }
    let wc = WS!(client);
    wc.is_connected().into()
}

#[no_mangle]
pub extern "C" fn create_client(opt: *mut signal_client_opt_t) -> *mut signal_client_t {
    let wc = Box::new(WsClient::new(unsafe { *opt }));
    let c = Box::new(signal_client_t {
        priv_: Box::into_raw(wc) as *mut c_void,
        destroy: Some(destroy),
        login: Some(login),
        create_session: Some(create_session),
        destroy_session: Some(destroy_session),
        update_local_sdp: Some(update_local_sdp),
        update_ice_candidate: Some(update_ice_candidate),
        handle_msg: Some(handle_msg),
        is_connected: Some(is_connected),
    });
    Box::into_raw(c)
}

pub extern "C" fn create_buffer_t(size: u32) -> buffer_t {
    let mut data: Vec<i8> = Vec::with_capacity(size as usize);
    let buff = buffer_t {
        data: data.as_mut_ptr() as *mut core::ffi::c_char,
        pos: 0,
        cap: size,
    };
    Vec::leak(data);
    buff
}

#[no_mangle]
pub extern "C" fn free_buffer_t(buffer: *mut buffer_t) {
    if !buffer.is_null() {
        let buff = unsafe { Box::from_raw(buffer) };
        if !buff.data.is_null() && buff.cap > 0 {
            unsafe { Box::from_raw(buff.data) };
        }
    }
}

#[no_mangle]
pub extern "C" fn free_buffer_t_content(buffer: *mut buffer_t) {
    if !buffer.is_null() {
        let buff = unsafe { &mut *buffer };
        if !buff.data.is_null() && buff.cap > 0 {
            // debugln!("free buffer data {:p} len {}", buff.data, buff.cap);
            unsafe { Box::from_raw(buff.data) };
            buff.data = null_mut();
            buff.pos = 0;
            buff.cap = 0;
        }
    }
}

impl From<Vec<u8>> for buffer_t {
    fn from(v: Vec<u8>) -> Self {
        let d = v.leak();
        Self {
            data: if d.len() == 0 {
                null_mut()
            } else {
                d.as_mut_ptr() as *mut c_char
            },
            pos: 0,
            cap: d.len() as u32,
        }
    }
}
